package handler

import (
	"UserServer/api/tool"
	"UserServer/model/request"
	"UserServer/model/response"
	"UserServer/rpc/pb"
	"errors"
	"fmt"
	"github.com/gin-contrib/sessions"
	"github.com/gin-gonic/gin"
	"github.com/google/uuid"
	"go.uber.org/zap"
	"google.golang.org/grpc"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/credentials/insecure"
	"google.golang.org/grpc/status"
	"time"
)

func GetUser(c *gin.Context) {
	claims := c.GetHeader("x-user-uuid")
	c.JSON(200, gin.H{
		"code": 200,
		"msg":  "success",
	})
	fmt.Println("测试案例:", claims)
}

// isValidPhone 判断手机号是否正确
func isValidPhone(phone string) bool {
	return len(phone) == 11 && string(phone[0]) == "1"
}

// SendEmail 点击发送qq邮箱
func SendEmail(c *gin.Context) {
	EmailReq := &pb.EmailReq{}
	err := c.ShouldBind(&EmailReq)
	if err != nil {
		response.FailWithMsg(c, "解析数据失败")
		return
	}
	err = tool.SendEmailVerificationCode(c, EmailReq.Email)
	if err != nil {
		response.FailWithMsg(c, "邮箱发送失败")
		return
	}
	response.SuccessWithMsg(c, "邮箱发送成功")
}

// Register 直接写成qq邮箱发送验证码登入得了
func Register(c *gin.Context) {
	// 首先先解析数据
	RegisterReq := &pb.RegisterReq{}
	err := c.ShouldBind(&RegisterReq)
	if err != nil {
		response.FailWithMsg(c, "解析数据失败")
		return
	}

	session := sessions.Default(c)

	// 两次邮箱一致性判断
	savedEmail := session.Get("email")
	if savedEmail == nil || savedEmail.(string) != RegisterReq.Email {
		response.FailWithMsg(c, "输入的邮箱和需要验证的邮箱不匹配")
		return
	}

	// 判断邮箱验证码是否过期
	savedTime := session.Get("expire_time")
	if savedTime.(int64) < time.Now().Unix() {
		response.FailWithMsg(c, "验证码已过期，请重新发送")
		return
	}

	Captcha := session.Get("verification_code")
	// 从会话中提取验证码
	// 如果存在，没有过期的话，判断输入的验证码和生成的验证码是否相同
	if RegisterReq.Captcha == Captcha {
		// 如果没有问题就返回
		if RegisterReq.Password1 != RegisterReq.Password2 {
			response.FailWithMsg(c, "两次输入的密码不同")
			return
		}
		// 远程调用rpc使用
		msg, err2 := Client.RegisterUser(c, RegisterReq)
		st, ok := status.FromError(err2)
		if ok {
			switch st.Code() {
			case codes.AlreadyExists:
				response.FailWithMsg(c, "该邮箱已经被注册")
				return
			case codes.Internal:
				SC.Log.Error("rpc内部出现问题：", zap.Error(err2))
				response.FailWithMsg(c, "rpc内部出现问题")
				return
			}
		}
		response.SuccessWithTotal(c, "注册成功", msg)
		return
	}
	// 如果不对，就返回验证码输入错误
	response.FailWithMsg(c, "验证码输入错误")
}

// Login 手机号登入
func Login(c *gin.Context) {
	LoginReq := &pb.LoginReq{}
	_ = c.ShouldBind(LoginReq)
	if isValidPhone(LoginReq.Phone) {
		// 如果正确
		result, err := Client.LoginUser(c, LoginReq)
		st, ok := status.FromError(err)

		if ok {
			switch st.Code() {
			case codes.NotFound:
				SC.Log.Info("该手机号未找到", zap.Error(err))
				response.FailWithMsg(c, "该手机号暂未注册")
				return
			case codes.Unknown:
				SC.Log.Info("输入的密码错误", zap.Error(err))
				response.FailWithMsg(c, "输入密码错误")
				return
			case codes.Internal:
				SC.Log.Error("rpc内部出现问题：", zap.Error(err))
				response.FailWithMsg(c, "rpc内部出现问题")
				return
			}
		}
		// 登入成功
		TokenNext(c, result)
	} else {
		response.FailWithMsg(c, "手机号格式不正确")
	}
}

// EmailLogin qq邮箱登入
func EmailLogin(c *gin.Context) {
	EmailLoginReq := &pb.EmailReq{}
	err := c.ShouldBind(&EmailLoginReq)
	if err != nil {
		response.FailWithMsg(c, "解析数据失败")
	}
	// 连接客户端数据
	result, err := Client.EmailLogin(c, EmailLoginReq)
	st, ok := status.FromError(err)
	if ok {
		switch st.Code() {
		case codes.NotFound:
			SC.Log.Info("该邮箱未找到", zap.Error(err))
			response.FailWithMsg(c, "该邮箱暂未注册")
			return
		case codes.Unknown:
			SC.Log.Info("输入的密码错误", zap.Error(err))
			response.FailWithMsg(c, "输入密码错误")
			return
		case codes.Internal:
			SC.Log.Error("rpc内部出现问题：", zap.Error(err))
			response.FailWithMsg(c, "rpc内部出现问题")
			return
		}
	}
	// 登入成功
	TokenNext(c, result)
}

// Logout 登出
func Logout(c *gin.Context) {
	// 说一下登出需要做什么：首先获取uuid和token
	// 接着将redis的这个token删除掉，
	// 将这个token在加入数据库的黑名单和缓存

	// 1.将这个请求传过去
	Uuid := tool.GetUUID(c)
	token := tool.GetRefreshToken(c)
	LogoutReq := &pb.LogoutReq{
		RefreshToken: token,
		Uuid:         Uuid,
	}

	_, err := Client.Logout(c, LogoutReq)
	st, ok := status.FromError(err)
	if ok {
		switch st.Code() {
		case codes.Internal:
			response.FailWithMsg(c, "退出失败")
			return
		}
	}
	// 成功之后在清除token
	tool.ClearRefreshToken(c)
	response.SuccessWithMsg(c, "退出成功")
}

// TokenNext 生成token
func TokenNext(c *gin.Context, user *pb.LoginAndRegisterResp) {
	// 1.创建对应的claims
	Uuid, _ := uuid.Parse(user.Uuid)
	accessClaims := &request.JwtAccessClaims{
		UserID: uint(user.Id),
		UUID:   Uuid,
	}
	refreshClaims := &request.JwtRefreshClaims{
		UUID: Uuid,
	}

	// 2.获得一个jwt对象
	j := tool.NewJWT()

	// 创建访问令牌
	accessToken, err := j.CreateAccessToken(*accessClaims)
	if err != nil {
		SC.Log.Error("Failed to get accessToken:", zap.Error(err))
		response.FailWithMsg(c, "Failed to get accessToken")
		return
	}

	// 创建刷新令牌
	refreshToken, err := j.CreateRefreshToken(*refreshClaims)
	if err != nil {
		SC.Log.Error("Failed to get refreshToken:", zap.Error(err))
		response.FailWithMsg(c, "Failed to get refreshToken")
		return
	}

	// 3.连接jwt的rpc客户端
	var ClientJwt pb.JwtClient
	conn, _ := grpc.NewClient(
		SC.Config.Etcd.Addr,
		grpc.WithTransportCredentials(insecure.NewCredentials()),
	)
	ClientJwt = pb.NewJwtClient(conn)
	// 最后关闭这个连接
	defer conn.Close()

	// 4.是否开启了多地点登录拦截
	if !SC.Config.System.Multipoint {
		// 设置刷新令牌并返回
		tool.SetToken(c, refreshToken, int(refreshClaims.ExpiresAt.Unix()-time.Now().Unix()))
		response.SuccessWithTotal(c, "Successful login", response.Login{
			User:                 user,
			AccessToken:          accessToken,
			AccessTokenExpiresAt: time.Now().Add(time.Hour*24).Unix() * 1000, // 24 小时过期,
		})
		return
	}
	// 开启
	// 检查 Redis 中是否已存在该用户的 JWT
	// 设置了两个，一个是黑名单，另一个是jwt
	// 黑名单是 jwt:"" 只需要判断有无 加上过期时间
	// refresh是 uuid:jwt 加上过期时间
	req := &pb.GetRedisJwtReq{
		Uuid: user.Uuid,
	}
	resp, err2 := ClientJwt.GetRedisJWT(c, req)
	if resp == nil {
		// 解析 gRPC 错误状态
		st, ok := status.FromError(err2)
		if ok {
			switch st.Code() {
			case codes.NotFound:
				// 不存在就设置新的，通过 uuid : jwt 的形式记录缓存
				duration, _ := tool.ParseDuration(SC.Config.Jwt.RefreshTokenExpireTime)
				req2 := &pb.SetRedisJwtReq{
					RefreshToken: refreshToken,
					Uuid:         user.Uuid,
					Duration:     duration.String(),
				}
				// 重新设置redis的token
				if _, err := ClientJwt.SetRedisJWT(c, req2); err != nil {
					SC.Log.Error("登入失败:", zap.Error(err))
					response.FailWithMsg(c, "登入失败")
					return
				}
				// 设置刷新令牌过期时间并返回
				// 设置浏览器里面的cookie，这里是refreshToken
				refreshEp, _ := tool.ParseDuration(SC.Config.Jwt.RefreshTokenExpireTime)
				accessEp, _ := tool.ParseDuration(SC.Config.Jwt.AccessTokenExpireTime)
				tool.SetToken(c, refreshToken, int(refreshEp))
				// 将这个uuid存入
				c.Set("claims", Uuid)
				// 把access给前端存储
				response.SuccessWithTotal(c, "登入成功", response.Login{
					User:                 user,
					AccessToken:          accessToken,
					AccessTokenExpiresAt: int64(accessEp),
				})
			case codes.Internal:
				// 如果是内部服务器错误
				SC.Log.Error("登入失败:", zap.Error(errors.New(st.Message())))
				response.FailWithMsg(c, "服务内部错误")
			default:
				SC.Log.Error("登入失败:", zap.Error(errors.New("未知错误")))
				response.FailWithMsg(c, "未知错误")
			}
		}
	} else {
		// Redis 中已存在该用户的 JWT 且未过期，则提示账号目前有人使用
		response.FailWithMsg(c, "登入失败，目前已经有人登入了哦")
	}
}
